/******************************************************************************
 * Copyright 2022 The Airos Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#include "gat_worker.h"

#include <sys/time.h>

#include <functional>
#include <iostream>
#include <sstream>

#include "glog/logging.h"

namespace os {
namespace v2x {
namespace device {

GatWorker::~GatWorker() { this->Stop(); }

bool GatWorker::Init(const std::string &remote_ip, const uint16_t remote_port,
                     const std::string &host_ip, const uint16_t host_port,
                     const std::string &protocol) {
  GatCommunication::ProtocolType protocol_type;
  if (protocol == "tcp") {
    protocol_type = GatCommunication::ProtocolType::TCP;
  } else if (protocol == "udp") {
    protocol_type = GatCommunication::ProtocolType::UDP;
  } else {
    LOG(ERROR) << "not support this protocol: " << protocol;
    return false;
  }

  communication_.reset(new GatCommunication());
  if (!communication_->Init(remote_ip, remote_port, host_ip, host_port,
                            protocol_type)) {
    LOG(ERROR) << "GatCommunication init error";
    return false;
  }
  monitor_.reset(new GatMonitor());
  if (!monitor_->Init()) {
    LOG(ERROR) << "GatMonitor init error";
    return false;
  }

  parser_.reset(new GatParser());
  if (!parser_->Init(monitor_)) {
    LOG(ERROR) << "GatParser init error";
    return false;
  }

  thread_process_recv_.reset(
      new std::thread(std::bind(&GatWorker::TaskProcessRecvFrame, this)));

  if (!this->InitTimerQueryColorState()) {
    return false;
  }

  if (!this->InitTimerQueryCurrPlanStep()) {
    return false;
  }

  if (!this->InitTimerKeepConnection()) {
    return false;
  }

  return true;
}

void GatWorker::Stop() {
  stop_ = true;

  if (timer_query_color_state_ != nullptr) {
    timer_delete(timer_query_color_state_);
  }

  if (timer_query_curr_plan_step_ != nullptr) {
    timer_delete(timer_query_curr_plan_step_);
  }

  if (timer_keep_connection_ != nullptr) {
    timer_delete(timer_keep_connection_);
  }

  if (thread_process_recv_ != nullptr && thread_process_recv_->joinable()) {
    thread_process_recv_->join();
  }
}

bool GatWorker::GetTrafficLightData(
    os::v2x::device::TrafficLightBaseData &lamp_info) {
  return parser_->GetTrafficLightData(lamp_info);
}

void GatWorker::TimeoutQueryColorState(__sigval_t arg) {
  GatWorker *worker = reinterpret_cast<GatWorker *>(arg.sival_ptr);

  uint8_t send_buff[80] = {0};

  size_t send_len =
      worker->parser_->MakePacketQueryColorState(send_buff, sizeof(send_buff));

  ssize_t ret_val = worker->SendFrame(send_buff, send_len);
  if (ret_val < 0) {
    LOG(ERROR) << "send query light state error.";
  }
}

void GatWorker::TimeoutQueryCurrPlanStep(__sigval_t arg) {
  GatWorker *worker = reinterpret_cast<GatWorker *>(arg.sival_ptr);

  uint8_t send_buff[80] = {0};

  size_t send_len = worker->parser_->MakePacketQueryCurrPlanStep(
      send_buff, sizeof(send_buff));

  ssize_t ret_val = worker->SendFrame(send_buff, send_len);
  if (ret_val < 0) {
    LOG(ERROR) << "send query curr period error.";
  }
}

void GatWorker::TimeoutKeepConnection(__sigval_t arg) {
  GatWorker *worker = reinterpret_cast<GatWorker *>(arg.sival_ptr);

  if (!worker->monitor_->IsRemoteAlive()) {
    if (!worker->communication_->Connect()) {
      LOG(ERROR) << "connect error.";
    }
  }
}

ssize_t GatWorker::SendFrame(uint8_t *packet_addr, size_t packet_len) {
  return communication_->SendData(packet_addr, packet_len);
}

void GatWorker::TaskProcessRecvFrame() {
  LOG(WARNING) << "start task : process response.";
  uint8_t recv_buff[2048] = {0};
  ssize_t recv_len = -1;
  while (!stop_) {
    recv_len = communication_->RecvDataWait(recv_buff, sizeof(recv_buff));
    if (recv_len > 0) {
      parser_->ProcessFrames(recv_buff, recv_len);
    }
  }

  LOG(WARNING) << "task over : process response.";
}

bool GatWorker::InitTimerQueryColorState() {
  struct sigevent evp;
  evp.sigev_notify = SIGEV_THREAD;
  evp.sigev_notify_function = GatWorker::TimeoutQueryColorState;
  evp.sigev_value.sival_ptr = this;
  evp.sigev_notify_attributes = NULL;

  if (0 != timer_create(CLOCK_REALTIME, &evp, &timer_query_color_state_)) {
    LOG(ERROR) << "timer create failed.";
    return false;
  }

  struct itimerspec ts;
  ts.it_interval.tv_sec = 0;
  ts.it_interval.tv_nsec = kQueryColorStateInterval * 1000000L;
  ts.it_value.tv_sec = 0;
  ts.it_value.tv_nsec = kQueryColorStateInterval * 1000000L;

  if (0 != timer_settime(timer_query_color_state_, 0, &ts, NULL)) {
    LOG(ERROR) << "timer set failed.";
    return false;
  }

  return true;
}

bool GatWorker::InitTimerQueryCurrPlanStep() {
  struct sigevent evp;
  evp.sigev_notify = SIGEV_THREAD;
  evp.sigev_notify_function = GatWorker::TimeoutQueryCurrPlanStep;
  evp.sigev_value.sival_ptr = this;
  evp.sigev_notify_attributes = NULL;

  if (0 != timer_create(CLOCK_REALTIME, &evp, &timer_query_curr_plan_step_)) {
    LOG(ERROR) << "timer create failed.";
    return false;
  }

  struct itimerspec ts;
  ts.it_interval.tv_sec = 1;
  ts.it_interval.tv_nsec = 0;
  ts.it_value.tv_sec = 1;
  ts.it_value.tv_nsec = 0;

  if (0 != timer_settime(timer_query_curr_plan_step_, 0, &ts, NULL)) {
    LOG(ERROR) << "timer set failed.";
    return false;
  }

  return true;
}

bool GatWorker::InitTimerKeepConnection() {
  struct sigevent evp;
  evp.sigev_notify = SIGEV_THREAD;
  evp.sigev_notify_function = GatWorker::TimeoutKeepConnection;
  evp.sigev_value.sival_ptr = this;
  evp.sigev_notify_attributes = NULL;

  if (0 != timer_create(CLOCK_REALTIME, &evp, &timer_keep_connection_)) {
    LOG(ERROR) << "timer create failed.";
    return false;
  }

  struct itimerspec ts;
  ts.it_interval.tv_sec = 3;
  ts.it_interval.tv_nsec = 0;
  ts.it_value.tv_sec = 3;
  ts.it_value.tv_nsec = 0;

  if (0 != timer_settime(timer_keep_connection_, 0, &ts, NULL)) {
    LOG(ERROR) << "timer set failed.";
    return false;
  }

  return true;
}

}  // namespace device
}  // namespace v2x
}  // namespace os